﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Aeon.Emulator.CommandInterpreter
{
    /// <summary>
    /// Contains methods for parsing command line statements.
    /// </summary>
    internal static class Parser
    {
        /// <summary>
        /// Contains all of the known command types.
        /// </summary>
        private static readonly Dictionary<string, Type> commands = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
        /// <summary>
        /// Delimiters for known commands.
        /// </summary>
        private static readonly char[] CommandDelimiters = new char[] { '.', '\\', '/', ' ', '\t' };

        /// <summary>
        /// Initializes static fields on the Parser class.
        /// </summary>
        static Parser()
        {
            var commandTypes = typeof(Command)
                .Assembly
                .GetTypes()
                .Where(t => t.IsSubclassOf(typeof(Command)));                

            foreach(var t in commandTypes)
            {
                object[] attrs = t.GetCustomAttributes(typeof(CommandAttribute), false);
                if(attrs != null)
                {
                    foreach(var cmdInfo in attrs.Cast<CommandAttribute>())
                        commands[cmdInfo.Name] = t;
                }
            }
        }

        /// <summary>
        /// Parses a command line string.
        /// </summary>
        /// <param name="commandLine">Command line string to parse.</param>
        /// <returns>Command instance generated from the command line if possible; otherwise null.</returns>
        public static Command Parse(string commandLine)
        {
            if(commandLine == null)
                throw new ArgumentNullException("commandLine");

            commandLine = commandLine.Trim();
            if(commandLine == string.Empty)
                return null;

            var chdrive = ParseChangeDriveCommand(commandLine);
            if(chdrive != null)
                return chdrive;

            var command = ParseKnownCommand(commandLine);
            if(command != null)
                return command;

            return ParseLaunchCommand(commandLine);
        }

        /// <summary>
        /// Attemps to parse a command line statement into a change drive command.
        /// </summary>
        /// <param name="commandLine">Command line string to parse.</param>
        /// <returns>Chdrive command instance if parsing was successful.</returns>
        private static Commands.Chdrive ParseChangeDriveCommand(string commandLine)
        {
            if(commandLine.Length == 2 && commandLine[1] == ':')
            {
                var chdriveCommand = new Commands.Chdrive();
                chdriveCommand.Parse(commandLine);
                return chdriveCommand;
            }

            return null;
        }
        /// <summary>
        /// Attempts to parse a command line statement into a known command.
        /// </summary>
        /// <param name="commandLine">Command line string to parse.</param>
        /// <returns>Command instance if a command was found; otherwise null.</returns>
        private static Command ParseKnownCommand(string commandLine)
        {
            string commandText = commandLine;
            string commandArgs = string.Empty;
            int index = commandLine.IndexOfAny(CommandDelimiters);
            if(index > 0)
            {
                commandText = commandLine.Substring(0, index);
                commandArgs = commandLine.Substring(index);
            }

            Type commandType;
            if(commands.TryGetValue(commandText, out commandType))
            {
                Command command = (Command)Activator.CreateInstance(commandType);
                command.Parse(commandArgs.Trim());
                return command;
            }

            return null;
        }
        /// <summary>
        /// Attempts to parse a command line statement into a launch command.
        /// </summary>
        /// <param name="commandLine">Command line statement to parse.</param>
        /// <returns>Launch command instance.</returns>
        private static Commands.Launch ParseLaunchCommand(string commandLine)
        {
            var launchCommand = new Commands.Launch();
            launchCommand.Parse(commandLine);
            return launchCommand;
        }
    }
}
